﻿// *********************************************************************
// [DCOM Productions]
// [Copyright (C) DCOM Productions All rights reserved.]
// *********************************************************************

namespace BitFlex.Windows.Forms {
    using System;
    using System.Windows.Forms;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Collections.ObjectModel;
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.Drawing.Drawing2D;
    using System.ComponentModel.Design;
    using System.Drawing.Design;

    /// <summary>
    /// A flexible control that can be used for multiple commands and actions.
    /// </summary>
    [ToolboxBitmap(typeof(Button))]
    public partial class HyperCommandLink : Control {
        /// <summary>
        /// Instantiates a new instance of the BitFlex.Windows.Forms.HyperCommandLink class.
        /// </summary>
        public HyperCommandLink() {
            InitializeComponent();

            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.ResizeRedraw, true);
            SetStyle(ControlStyles.UserPaint, true);

            UpdateStyles(); 
        }

        #region Platform Invoke

        [System.Runtime.InteropServices.DllImport("comctl32.dll")]
        private static extern Int32 DllGetVersion(ref DLLVERSIONINFO version);

        [System.Runtime.InteropServices.DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibrary(String path);

        [System.Runtime.InteropServices.DllImport("uxtheme.dll")]
        private static extern Boolean IsAppThemed();

        [System.Runtime.InteropServices.DllImport("uxtheme.dll")]
        private static extern Boolean IsThemeActive();

        #endregion

        #region Constants

        private readonly Color BorderColor = Color.FromArgb(218, 242, 252);
        private readonly Color BorderFocusedColor = Color.FromArgb(200, 200, 200);
        private readonly Color BorderHotColor = Color.FromArgb(168, 224, 247);
        private readonly Color PrimaryBackgroundColor = Color.FromArgb(255, 255, 255);
        private readonly Color PrimarySublinkColor = Color.FromArgb(0, 102, 204);
        private readonly Color PrimaryTextColor = Color.FromArgb(0, 110, 18);
        private readonly Color SecondaryBackgroundColor = Color.FromArgb(240, 250, 255);
        private readonly Color SecondarySublinkColor = Color.FromArgb(51, 153, 255);
        private readonly Color SecondaryTextColor = Color.FromArgb(0, 174, 29);
        private const Single SystemTextFontSize = 9F;
        private const Single TextFontSize = 11F;

        #endregion

        #region Fields

        private HyperCommandSubLink m_ActiveSublink;
        private Boolean m_HasTabFocus;
        private Point m_MouseLocation;
        private Boolean m_MouseHovering;
        private Boolean m_MousePressed;

        #endregion

        #region Properties

        private Image m_Image = null;
        /// <summary>
        /// Gets or Sets the control's image
        /// </summary>
        [
            Category("Appearance"),
            Description("Gets or Sets the control's image")
        ]
        public Image Image {
            get {
                return m_Image;
            }
            set {
                m_Image = value;
                Invalidate();
            }
        }

        private Collection<HyperCommandSubLink> m_Links;
        /// <summary>
        /// Gets or Sets the collection of clickable links displayed on the control
        /// </summary>
        [
            Category("Data"),
            Description("Gets or Sets the collection of clickable links displayed on the control"),
            DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
            Editor(typeof(CollectionEditor), typeof(UITypeEditor)),
            NotifyParentProperty(true)
        ]
        public Collection<HyperCommandSubLink> Links {
            get {
                if (m_Links == null) {
                    m_Links = new Collection<HyperCommandSubLink>();
                }
                return m_Links;
            }
        }

        private String m_Note = String.Empty;
        /// <summary>
        /// Gets or Sets the note used to describe a specific action
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(""),
            Description("Gets or Sets the note used to describe a specific action"),
            Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))
        ]
        public String Note {
            get {
                return m_Note;
            }
            set {
                m_Note = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or Sets the control's text
        /// </summary>
        [
            Category("Appearance")
        ]
        public override string Text {
            get {
                return base.Text;
            }
            set {
                base.Text = value;
                Invalidate();
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when the control's image is clicked
        /// </summary>
        [
            Category("Action"),
            Description("Occurs when the control's image is clicked")
        ]
        public event System.EventHandler<System.EventArgs> ImageClicked;
        private void OnImageClicked(Object sender, System.EventArgs e) {
            if (ImageClicked != null) {
                ImageClicked(sender, e);
            }
        }

        /// <summary>
        /// Occurs when a sublink is clicked
        /// </summary>
        [
            Category("Action"),
            Description("Occurs when a sublink is clicked")
        ]
        public event System.EventHandler<HyperCommandSubLinkClickedEventArgs> SublinkClicked;
        private void OnSubLinkClicked(Object sender, HyperCommandSubLinkClickedEventArgs e) {
            if (SublinkClicked != null) {
                SublinkClicked(sender, e);
            }
        }

        /// <summary>
        /// Occurs when the control's text is clicked
        /// </summary>
        [
            Category("Action"),
            Description("Occurs when the control's text is clicked")
        ]
        public event System.EventHandler<System.EventArgs> TextClicked;
        private void OnTextClicked(Object sender, System.EventArgs e) {
            if (TextClicked != null) {
                TextClicked(sender, e);
            }
        }

        #endregion

        #region Base Overrides

        /// <summary>
        /// Overrides the base implementation of Control.OnClick
        /// </summary>
        protected override void OnClick(EventArgs e) {
            if (GetImageRectangle().Contains(m_MouseLocation)) {
                OnImageClicked(Image, e);
            }
            else if (GetTextRectangle().Contains(m_MouseLocation)) {
                OnTextClicked(Text, e);
            }
            else if (m_ActiveSublink == null) {
                base.OnClick(e);
            }
            else {
                OnSubLinkClicked(m_ActiveSublink, new HyperCommandSubLinkClickedEventArgs(m_ActiveSublink.Command));
            }
        }

        /// <summary>
        /// Overrides the base implementation of Control.OnEnter
        /// </summary>
        protected override void OnEnter(EventArgs e) {
            base.OnEnter(e);

            m_HasTabFocus = true;
            Invalidate();
        }

        /// <summary>
        /// Overrides the base implementation of Control.OnKeyPress
        /// </summary>
        protected override void OnKeyPress(KeyPressEventArgs e) {
            if (e.KeyChar == 13) {
                e.Handled = true;
                OnClick(e);
            }
            else {
                base.OnKeyPress(e);
            }
        }

        /// <summary>
        /// Overrides the base implementation of Control.OnLeave
        /// </summary>
        protected override void OnLeave(EventArgs e) {
            base.OnLeave(e);

            m_HasTabFocus = false;
            Invalidate();
        }

        /// <summary>
        /// Overrides the base implementation of Control.OnMouseDown
        /// </summary>
        protected override void OnMouseDown(MouseEventArgs e) {
            base.OnMouseDown(e);

            m_MousePressed = true;
            Invalidate();
        }

        /// <summary>
        /// Overrides the base implementation of Control.OnMouseEnter
        /// </summary>
        protected override void OnMouseEnter(EventArgs e) {
            base.OnMouseEnter(e);

            m_MouseHovering = true;
            Invalidate();
        }

        /// <summary>
        /// Overrides the base implementation of Control.OnMouseLeave
        /// </summary>
        protected override void OnMouseLeave(EventArgs e) {
            base.OnMouseLeave(e);

            m_MouseHovering = false;
            Invalidate();
        }

        /// <summary>
        /// Overrides the base implementation of Control.OnMouseMove
        /// </summary>
        protected override void OnMouseMove(MouseEventArgs e) {
            base.OnMouseMove(e);

            m_MouseLocation = e.Location;
            Invalidate();
        }

        /// <summary>
        /// Overrides the base implementation of Control.OnMouseUp
        /// </summary>
        protected override void OnMouseUp(MouseEventArgs e) {
            base.OnMouseUp(e);

            m_MousePressed = false;
            Invalidate();
        }

        /// <summary>
        /// Overrides the base implementation of Control.OnPaint
        /// </summary>
        protected override void OnPaint(PaintEventArgs e) {
            base.OnPaint(e);

            DrawBackground(e);

            DrawBorder(e);

            DrawImage(e);

            DrawText(e);

            DrawNote(e);

            DrawLinks(e);

            NotifyUser();

            AutoSize();
        }

        #endregion

        #region Methods

        /// <devdoc>
        /// Here we automatically size the control's height based upon the control's text, note, links and image
        /// </devdoc>
        new private void AutoSize() {
            Int32 bottom = 0;

            if (Image != null) {
                bottom += Image.Height;
                bottom += 4;
            }

            if (GetNoteRectangle().Bottom > bottom) {
                if (Image != null) {
                    bottom -= Image.Height;
                    bottom -= 4;
                }

                bottom += GetNoteRectangle().Bottom;
            }

            if (Links.Count > 0) {
                int linkAreaHeight = 0;

                foreach (HyperCommandSubLink item in Links) {
                    using (Font font = new Font(Font.FontFamily, 9F, FontStyle.Underline)) {
                        using (Graphics g = CreateGraphics()) {
                            SizeF layout = g.MeasureString(item.Text, font);
                            linkAreaHeight += (int)layout.Height;
                            linkAreaHeight += 4;
                        }
                    }
                }

                bottom += linkAreaHeight;
                bottom += 4;
            }
            else {
                bottom += 4;
            }

            Height = bottom;
        }

        private void DrawBackground(PaintEventArgs e) {
            if (IsVisualStylesEnabled()) {
                if (m_MouseHovering) {
                    using (GraphicsPath path = new GraphicsPath()) {
                        path.AddArc(ClientRectangle.X, ClientRectangle.Y, 7, 7, 180, 90);
                        path.AddLine(4, 0, ClientRectangle.Width - 4, 0);
                        path.AddArc(ClientRectangle.X + ClientRectangle.Width - 8, ClientRectangle.Y, 7, 7, 270, 90);
                        path.AddLine(ClientRectangle.Width - 1, 4, ClientRectangle.Width - 1, ClientRectangle.Height - 4);
                        path.AddArc(ClientRectangle.X + ClientRectangle.Width - 8, ClientRectangle.Y + ClientRectangle.Height - 8, 7, 7, 0, 90);
                        path.AddLine(4, ClientRectangle.Height - 1, ClientRectangle.Width - 4, ClientRectangle.Height - 1);
                        path.AddArc(ClientRectangle.X, ClientRectangle.Y + ClientRectangle.Height - 8, 7, 7, 90, 90);
                        path.AddLine(ClientRectangle.X, 4, ClientRectangle.X, ClientRectangle.Height - 4);

                        using (LinearGradientBrush brush = new LinearGradientBrush(ClientRectangle, PrimaryBackgroundColor, SecondaryBackgroundColor, 90F)) {
                            e.Graphics.FillPath(brush, path);
                        }
                    }
                }
            }
        }

        private void DrawBorder(PaintEventArgs e) {
            if (IsVisualStylesEnabled()) {
                using (Pen pen = new Pen(BorderColor)) {
                    if (m_HasTabFocus && m_MouseHovering) {
                        pen.Color = BorderHotColor;
                    }
                    else if (m_HasTabFocus) {
                        pen.Color = BorderFocusedColor;
                    }

                    if (m_HasTabFocus || m_MouseHovering) {
                        e.Graphics.DrawArc(pen, ClientRectangle.X, ClientRectangle.Y, 7, 7, 180, 90);
                        e.Graphics.DrawLine(pen, 4, 0, ClientRectangle.Width - 4, 0);
                        e.Graphics.DrawArc(pen, ClientRectangle.X + ClientRectangle.Width - 8, ClientRectangle.Y, 7, 7, 270, 90);
                        e.Graphics.DrawLine(pen, ClientRectangle.Width - 1, 4, ClientRectangle.Width - 1, ClientRectangle.Height - 4);
                        e.Graphics.DrawArc(pen, ClientRectangle.X + ClientRectangle.Width - 8, ClientRectangle.Y + ClientRectangle.Height - 8, 7, 7, 0, 90);
                        e.Graphics.DrawLine(pen, 4, ClientRectangle.Height - 1, ClientRectangle.Width - 4, ClientRectangle.Height - 1);
                        e.Graphics.DrawArc(pen, ClientRectangle.X, ClientRectangle.Y + ClientRectangle.Height - 8, 7, 7, 90, 90);
                        e.Graphics.DrawLine(pen, ClientRectangle.X, 4, ClientRectangle.X, ClientRectangle.Height - 4);
                    }
                }
            }
        }

        private void DrawImage(PaintEventArgs e) {
            if (Image != null) {
                using (Bitmap bitmap = new Bitmap(48, 48)) {
                    using (Graphics g = Graphics.FromImage(bitmap)) {
                        g.CompositingQuality = CompositingQuality.HighQuality;
                        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        g.DrawImage(Image, 0, 0, 48, 48);

                        e.Graphics.DrawImage(bitmap, GetImageRectangle());
                    }
                }
            }
        }

        private void DrawLink(HyperCommandSubLink item, Int32 x, ref Int32 y, PaintEventArgs e) {
            if (item.Image != null) {
                Rectangle imageArea = new Rectangle();

                imageArea.X = x;
                imageArea.Y = y;

                imageArea.Height = 16;
                imageArea.Width = 16;

                using (Bitmap bitmap = new Bitmap(16, 16)) {
                    using (Graphics g = Graphics.FromImage(bitmap)) {
                        g.CompositingQuality = CompositingQuality.HighQuality;
                        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        g.DrawImage(item.Image, 0, 0, 16, 16);

                        e.Graphics.DrawImage(bitmap, imageArea);
                    }
                }

                x += 16; // image width
                x += 4; // padding
            }

            using (Font measureFont = new Font(Font.FontFamily, 9F)) {
                SizeF linkSize = e.Graphics.MeasureString(item.Text, measureFont);
                Rectangle linkArea = new Rectangle(x, y, (Int32)linkSize.Width, (Int32)linkSize.Height);

                if (linkArea.Contains(m_MouseLocation)) {
                    using (Font font = new Font(measureFont.FontFamily, measureFont.Size, FontStyle.Underline)) {
                        if (IsVisualStylesEnabled()) {
                            using (SolidBrush brush = new SolidBrush(SecondarySublinkColor)) {
                                e.Graphics.DrawString(item.Text, font, brush, linkArea.X, linkArea.Y);
                            }
                        }
                        else {
                            using (SolidBrush brush = new SolidBrush(SystemColors.HotTrack)) {
                                e.Graphics.DrawString(item.Text, font, brush, linkArea.X, linkArea.Y);
                            }
                        }
                    }

                    m_ActiveSublink = item;
                }
                else {
                    if (IsVisualStylesEnabled()) {
                        using (SolidBrush brush = new SolidBrush(PrimarySublinkColor)) {
                            e.Graphics.DrawString(item.Text, measureFont, brush, linkArea.X, linkArea.Y);
                        }
                    }
                    else {
                        using (SolidBrush brush = new SolidBrush(SystemColors.HotTrack)) {
                            e.Graphics.DrawString(item.Text, measureFont, brush, linkArea.X, linkArea.Y);
                        }
                    }
                }

                y += linkArea.Height;

                if (IsVisualStylesEnabled()) {
                    y += 4;
                }
            }
        }

        private void DrawLinks(PaintEventArgs e) {
            if (m_ActiveSublink != null) {
                m_ActiveSublink = null;
            }

            if (Links.Count > 0) {
                Int32 x = 0, y;

                if (String.IsNullOrEmpty(Note)) {
                    y = GetTextRectangle().Bottom + 4;
                }
                else {
                    y = GetNoteRectangle().Bottom + 4;
                } 

                if (Image != null) {
                    x = GetImageRectangle().Right + 4;
                }
                else {
                    x = 4;
                }

                foreach (HyperCommandSubLink item in Links) {
                    DrawLink(item, x, ref y, e);
                }
            }
        }

        private void DrawNote(PaintEventArgs e) {
            if (!String.IsNullOrEmpty(Note)) {
                Rectangle noteArea = GetNoteRectangle();
                e.Graphics.DrawString(Note, Font, SystemBrushes.ControlText, (RectangleF)noteArea);
            }
        }

        private void DrawText(PaintEventArgs e) {
            if (!String.IsNullOrEmpty(Text)) {
                Rectangle textArea = GetTextRectangle();

                using (SolidBrush brush = new SolidBrush(PrimaryTextColor)) {
                    if (textArea.Contains(m_MouseLocation)) {
                        brush.Color = SecondaryTextColor;

                        if (IsVisualStylesEnabled()) {
                            using (Font font = new Font(Font.FontFamily, TextFontSize, FontStyle.Underline)) {
                                e.Graphics.DrawString(Text, font, brush, (RectangleF)textArea);
                            }
                        }
                        else {
                            // No visual styles, use system color for the text
                            brush.Color = SystemColors.HotTrack;

                            using (Font font = new Font(Font.FontFamily, SystemTextFontSize, FontStyle.Bold | FontStyle.Underline)) {
                                e.Graphics.DrawString(Text, font, brush, (RectangleF)textArea);
                            }
                        }
                    }
                    else {
                        if (IsVisualStylesEnabled()) {
                            using (Font font = new Font(Font.FontFamily, TextFontSize)) {
                                e.Graphics.DrawString(Text, font, brush, (RectangleF)textArea);
                            }
                        }
                        else {
                            // No visual styles, use system color for the text
                            brush.Color = SystemColors.HotTrack;

                            using (Font font = new Font(Font.FontFamily, SystemTextFontSize, FontStyle.Bold)) {
                                e.Graphics.DrawString(Text, font, brush, (RectangleF)textArea);
                            }
                        }
                    }
                }
            }
        }

        private Rectangle GetImageRectangle() {
            Rectangle retVal = new Rectangle();

            if (Image != null) {
                retVal.X = 4;
                retVal.Y = 4;

                retVal.Width = Image.Width;
                retVal.Height = Image.Height;
            }
           
            return retVal;
        }

        private Rectangle GetNoteRectangle() {
            Rectangle retVal = new Rectangle();

            if (String.IsNullOrEmpty(Note)) {
                retVal.X = GetTextRectangle().X;
                retVal.Y = GetTextRectangle().Bottom + 4;

                retVal.Width = 0;
                retVal.Height = 0;

                return retVal;
            }
            else {
                Int32 maximumWidth = 0;

                if (Image == null) {
                    retVal.X = 4;

                    if (IsVisualStylesEnabled()) {
                        retVal.Y = GetTextRectangle().Bottom + 4;
                    }
                    else {
                        retVal.Y = GetTextRectangle().Bottom - 4;
                    }

                    // Subtract left padding and right padding from width
                    maximumWidth = ClientRectangle.Width - 4 - 5;
                }
                else {
                    retVal.X = GetImageRectangle().Right + 4;

                    if (IsVisualStylesEnabled()) {
                        retVal.Y = GetTextRectangle().Bottom + 4;
                    }
                    else {
                        retVal.Y = GetTextRectangle().Bottom - 4;
                    }

                    // Subtract image width and padding, left and right padding from width
                    maximumWidth = (ClientRectangle.Width - Image.Width) - 4 - 5 - 4;
                }

                using (Graphics g = CreateGraphics()) {
                    SizeF layout = g.MeasureString(Note, Font, maximumWidth);
                    retVal.Height = (Int32)layout.Height;
                    retVal.Width = (Int32)layout.Width + 4;
                }
            }

            return retVal;
        }

        private Rectangle GetTextRectangle() {
            Rectangle retVal = new Rectangle();

            if (Image == null) {
                retVal.X = 4;
                retVal.Y = 4;
            }
            else {
                retVal.X = GetImageRectangle().Right + 4;
                retVal.Y = GetImageRectangle().Y;
            }

            if (String.IsNullOrEmpty(Text)) {
                retVal.Height = 0;
                retVal.Width = 0;
            }
            else {
                using (Graphics g = CreateGraphics()) {
                    using (Font font = new Font(Font.FontFamily, TextFontSize)) {
                        SizeF layout = g.MeasureString(Text, font);
                        retVal.Height = (Int32)layout.Height;
                        retVal.Width = (Int32)layout.Width;

                        // Add 1 to compensate for float to int conversion
                        retVal.Width++;
                    }
                }
            }

            return retVal;
        }

        private Boolean IsVisualStylesEnabled() {
            if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major >= 5) {
                if (IsThemeActive() && IsAppThemed()) {
                    return true;
                }
            }

            return false;
        }

        private Boolean NotifyImage() {
            if (GetImageRectangle().Contains(m_MouseLocation)) {
                Cursor = Cursors.Hand;
                return true;
            }

            return false;
        }

        private Boolean NotifyLinks() {
            if (m_ActiveSublink != null) {
                Cursor = Cursors.Hand;
                return true;
            }

            return false;
        }

        private Boolean NotifyText() {
            if (GetTextRectangle().Contains(m_MouseLocation)) {
                Cursor = Cursors.Hand;
                return true;
            }

            return false;
        }

        /// <devdoc>
        /// This method will be used to display the appropriate cursor as the user 'navigates'
        /// the control's ux
        /// 
        /// 1. Notify image
        /// 2. Notify text
        /// 3. Notify links
        /// </devdoc>
        private void NotifyUser() {
            if (NotifyImage()) {
                return;
            }

            if (NotifyText()) {
                return;
            }

            if (NotifyLinks()) {
                return;
            }

            Cursor = Cursors.Default;
        }

        #endregion

        #region Nested Types

        [StructLayout(LayoutKind.Sequential)]
        private struct DLLVERSIONINFO {
            public Int32 cbSize;
            public Int32 dwMajorVersion;
            public Int32 dwMinorVersion;
            public Int32 dwBuildNumber;
            public Int32 dwPlatformID;
        }

        #endregion
    }
}